package maze;

import java.util.LinkedList;
import java.util.Scanner;

public class Maze implements Cloneable {

	private int id;
	private int[][] board;
	private LinkedList<Tile> list;
	private int width;
	private int height;
	private Cell goal;

	/**
	 * instantiates the maze by calling the method createMaze()
	 */
	public Maze() {
		createMaze();
	}
	
	/**
	 * creates the maze by taking its instance variables from the user,
	 * 
	 * the input it takes are width, height, number of tiles, exit cell and then 
	 * 
	 * the attributes of each tile which are: start, end, orientation and type
	 */
	public void createMaze() {
		Scanner sc = new Scanner(System.in);
		// System.out.print("Width: ");
		width = sc.nextInt();
		// System.out.print("Height: ");
		height = sc.nextInt();
		// System.out.print("Number of tiles: ");
		int n = sc.nextInt();
		// System.out.print("Exit cell (x y): ");
		int exitX = sc.nextInt();
		int exitY = sc.nextInt();
		goal = new Cell(exitX, exitY);
		int id = 1;
		list = new LinkedList<Tile>();
		while (n-- > 0) {
			// System.out.print("Start x & y: ");
			int sx = sc.nextInt();
			int sy = sc.nextInt();
			// System.out.print("End x & y: ");
			int ex = sc.nextInt();
			int ey = sc.nextInt();
			// System.out.print("Orientation (H or V): ");
			String orient = sc.next();

			// System.out.print(" Tile type (M -> mouse, T -> tile): ");
			String type = sc.next();
			Tile t = new Tile(id, new Cell(sx, sy), new Cell(ex, ey), orient,
					(type.equals("M") ? true : false));
			list.addLast(t);
			id++;
		}
		generateBoard();
	}
	
	/**
	 * creates the board by placing the id of each tile at the tile's location and 
	 * with the right size and orientation 
	 */
	public void generateBoard() {
		board = new int[height][width];
		for (int i = 0; i < list.size(); i++) {
			Tile t = list.get(i);
			if (t.getOrientation().equals("H")) {
				board[t.getStart().getX()][t.getStart().getY()] = t.getID();
				board[t.getEnd().getX()][t.getEnd().getY()] = t.getID();
			} else {
				if (t.getEnd().getX() - t.getStart().getX() == 1) {
					board[t.getStart().getX()][t.getStart().getY()] = t.getID();
					board[t.getEnd().getX()][t.getEnd().getY()] = t.getID();
				} else {
					board[t.getStart().getX()][t.getStart().getY()] = t.getID();
					board[t.getStart().getX() + 1][t.getStart().getY()] = t
							.getID();
					board[t.getEnd().getX()][t.getEnd().getY()] = t.getID();
				}
			}
		}
	}

	public int[][] getBoard() {
		return board;
	}

	public void setBoard(int[][] board) {
		this.board = board;
	}
	
	/**
	 *  displays the maze by printing the contents of the board, the free cells are
	 *  represented with '-' , each tile is represented with its id at each cell 
	 *  it occupies, the mouse is represented with 2 'M's and the goal( exit ) 
	 *  is represented with 'E'
	 */
	public void display() {
		for (int i = 0; i < board.length; i++) {
			for (int j = 0; j < board[i].length; j++) {
				if (goal.getX() == i && goal.getY() == j) {
					System.out.print("E ");
				} else if (board[i][j] == 0) {
					System.out.print("- ");
				} else if (isMouseID(board[i][j])) {
					System.out.print("M ");
				} else {
					System.out.print(board[i][j] + " ");
				}
			}
			System.out.println();
		}
	}
	/**
	 * checks whether the tile with the specified id is a mouth or a normal tile
	 * 
	 * @param id
	 * 
	 * @return boolean the result of the check
	 */
	public boolean isMouseID(int id) {
		for (int i = 0; i < list.size(); i++) {
			if (list.get(i).isMouse() && list.get(i).getID() == id) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * checks the applicability of the operator on the specified tile,
	 * 
	 * a check is made for the two orientations, the direction has to be consistent
	 * with the orientation e.g if Vertical tile then only Up and Down are considered,
	 *
	 * finally it checks if the destination cell is free 
	 * 
	 * @param id the identification of the tile to be moved
	 * @param operator represents the direction of the movement Up, Right, Down, Left
	 * 
	 * @return boolean the result of the applicability of the move
	 */
	public boolean canMove(int id, String operator) {

		Tile t = list.get(id - 1);
		if (t.getOrientation().equals("H")) {

			if (operator.equals("Right")) {
				int x = t.getStart().getX();
				int endY = t.getEnd().getY();
				if (!(endY + 1 < width && board[x][endY + 1] == 0)) {
					return false;
				}

			} else if (operator.equals("Left")) {

				int x = t.getStart().getX();
				int y = t.getStart().getY();

				if (!(y - 1 > -1 && board[x][y - 1] == 0)) {
					return false;
				}

			} else {
				return false;
			}
		} else {
			if (t.getOrientation().equals("V")) {
				if (operator.equals("Up")) {
					int x = t.getStart().getX();
					int y = t.getStart().getY();
					if (!(x - 1 > -1 && board[x - 1][y] == 0)) {
						return false;
					}

				} else if (operator.equals("Down")) {
					int y = t.getStart().getY();
					int endX = t.getEnd().getX();
					if (!(endX + 1 < height && board[endX + 1][y] == 0)) {
						return false;
					}
				} else {
					return false;
				}
			}
		}
		return true;
	}
	/**
	 * checks the applicability of the operator on the specified tile,
	 * 
	 * a check is made for the two orientations, the direction has to be consistent
	 * with the orientation e.g if Vertical tile then only Up and Down are considered,
	 *  
	 * after those consideration if the destination cell is free, 
	 * then the tile is moved in the given direction 
	 * 
	 * @param id the identification of the tile to be moved
	 * @param operator represents the direction of the movement Up, Right, Down, Left
	 * 
	 * @return boolean the result of the move
	 */
	public boolean move(int id, String operator) {
		Tile t = list.get(id - 1);
		if (t.getOrientation().equals("H")) {

			if (operator.equals("Right")) {
				int x = t.getStart().getX();
				int y = t.getStart().getY();

				int endY = t.getEnd().getY();
				if (endY + 1 < width && board[x][endY + 1] == 0) {

					t.setStart(new Cell(x, y + 1));
					t.setEnd(new Cell(x, endY + 1));
				} else {
					return false;
				}

			} else if (operator.equals("Left")) {

				int x = t.getStart().getX();
				int y = t.getStart().getY();

				int endY = t.getEnd().getY();
				if (y - 1 > -1 && board[x][y - 1] == 0) {
					t.setStart(new Cell(x, y - 1));
					t.setEnd(new Cell(x, endY - 1));
				} else {
					return false;
				}

			} else {
				return false;
			}
		} else {
			if (t.getOrientation().equals("V")) {
				if (operator.equals("Up")) {
					int x = t.getStart().getX();
					int y = t.getStart().getY();

					int endX = t.getEnd().getX();
					if (x - 1 > -1 && board[x - 1][y] == 0) {

						t.setStart(new Cell(x - 1, y));
						t.setEnd(new Cell(endX - 1, y));
					} else {
						return false;
					}

				} else if (operator.equals("Down")) {
					int x = t.getStart().getX();
					int y = t.getStart().getY();

					int endX = t.getEnd().getX();
					if (endX + 1 < height && board[endX + 1][y] == 0) {

						t.setStart(new Cell(x + 1, y));
						t.setEnd(new Cell(endX + 1, y));
					} else {
						return false;
					}

				} else {
					return false;
				}

			}
		}
		generateBoard();
		return true;

	}
	
	/**
	 * overrides the equal method, it checks that the given maze is of the 
	 * same width and height and then compares every two corresponding cells with 
	 * each other
	 * 
	 * @param object: the object to be compared
	 * 
	 * @return boolean the result of the comparison
	 */
	public boolean equals(Object o) {
		Maze m = (Maze) o;
		if (this.board.length != m.board.length
				|| this.board[0].length != m.board[0].length) {
			return false;
		}
		for (int i = 0; i < this.board.length; i++) {
			for (int j = 0; j < this.board[i].length; j++) {
				if (this.board[i][j] != m.board[i][j])
					return false;
			}
		}
		return true;
	}

	public LinkedList<Tile> getList() {
		return list;
	}

	public void setId(int id) {
		this.id = id;
	}

	public int getId() {
		return id;
	}

	public Cell getGoal() {
		return goal;
	}

	public int getHeight() {
		return height;
	}
	
	/**
	 * creates a clone copy of the maze	 
	 * @return
	 * @throws CloneNotSupportedException
	 */
	@SuppressWarnings("unchecked")
	public Maze copy() throws CloneNotSupportedException {
		Maze m = (Maze) this.clone();
		LinkedList<Tile> l = (LinkedList<Tile>) this.list.clone();
		m.list = new LinkedList<Tile>();
		while (!l.isEmpty()) {
			Tile t = l.removeFirst();
			m.list.addLast(new Tile(t.getID(), new Cell(t.getStart().getX(), t
					.getStart().getY()), new Cell(t.getEnd().getX(), t.getEnd()
					.getY()), t.getOrientation(), t.isMouse()));
		}
		return m;
	}

//	public static void main(String[] args) throws CloneNotSupportedException {
//		System.out.println("Input Maze");
//		Maze m = new Maze();
//		m.display();
//
//		m.move(4, "Left");
//		m.move(7, "Down");
//		m.display();
//		m.move(7, "Down");
//		m.display();
//
//		m.move(4, "Left");
//		System.out.println(m.move(1, "Right"));
//		m.display();
//		System.out.println(m.move(1, "Left"));
//		System.out.println(m.move(1, "Up"));
//		System.out.println(m.move(2, "Up"));
//		m.display();
//		System.out.println(m.move(2, "Down"));
//		m.display();
//		System.out.println(m.move(2, "Up"));
//		m.display();
//
//	}

}
